#ifndef funduspreprocessingH
#define funduspreprocessingH

#include <vector>
#include <string>

#include "Image.H"
#include "PixelIterator.H"
#include "Util.H"
#include "IO.H"



// Return minimum and maximum of the input image
template<class T>
void getMinMaxAvrImage(
const pip::Image<T> &in,
T &min, T &max, T &avr
)
{
        min = max = in(0);
        int numPixels = in.dim(2)*in.dim(1)*in.dim(0);
        avr = (T)0;
        for (int iz=0;iz<in.dim(2);++iz)
        for (int iy=0;iy<in.dim(1);++iy)
        for (int ix=0;ix<in.dim(0);++ix) {
                T f = in(ix,iy,iz);
                if (f<min) min = f;
                if (f>max) max = f;
                avr += f;
        }
        if (numPixels > 0)
        avr /= numPixels;
}

template<class Tout>
class PreProcessFundus {
        private:
        // Extract one color channel from a RGB image.
        // input:  RGB Image imIn
        // output: Image inOut
        // upon successful completion, the function returns 0, else it gives 1.
        int extractColorChannel(const pip::Image<pip::Color<unsigned char> >& imIn, pip::Image<unsigned char>& imOut);
        char color; // color channel to use ('R' = red, 'G' = green, 'B' = blue)

        // Median filter an image
        // input:  Image imInOut
        // output: Image imInOut
        // Upon successful completion, the function returns 0, else it gives 1.
        int medianFilter(pip::Image<unsigned char>& imInOut);
        int iMedianWidth; // width of the median kernel (square kernel is assumed).

        public:
        PreProcessFundus(): color('R'), iMedianWidth(15){};
        // Run the algorithm
        // input:  RGB Image imIn
        // output: Image inOut
        // Upon successful completion, the function returns 0, else it gives 1.
        int run(const pip::Image<pip::Color<unsigned char> >& imIn, pip::Image<Tout>& imOut);

        // Set the color channel to use ('R' = red, 'G' = green, 'B' = blue).
        // Return the actual color channel set.
        char setColorChannel(char _color){
                color = _color;
                if(color != 'R' && color != 'G' && color != 'B')
                color = 'R';
                return(color);
        }

        // Set the width of the median to use. Return the actual median width.
        int setMedianWidth(int _iMedianWidth){
                iMedianWidth = _iMedianWidth;
                return(iMedianWidth);
        }
};

template<class Tout>
int PreProcessFundus<Tout>::extractColorChannel(const pip::Image<pip::Color<unsigned char> >& imIn, pip::Image<unsigned char>& imOut) {

        // Resize out, if we must
        if (imOut.dimension()!= imIn.dimension())
        imOut.resize(imIn.dimension());

        pip::Dimension row = 0;
        PixelIterator<pip::Color<unsigned char> > i;
        PixelIterator<unsigned char> j;

        switch(color) {
            // Red
            case 'R':
                do{
                    i = imIn.begin(row); j = imOut.begin(row);
                    while(i!= imIn.end(row)) {
                        (*j) = (*i).red();
                        ++i; ++j;
                    }
                }
                while (imIn.nextRow(row));

            break;

            // Green
            case 'G':
                do{
                    i = imIn.begin(row); j = imOut.begin(row);
                    while(i!= imIn.end(row)) {
                        (*j) = (*i).green();
                        ++i; ++j;
                    }
                }
                while (imIn.nextRow(row));
            break;

            // Blue
            case 'B':
                do{
                    i = imIn.begin(row); j = imOut.begin(row);
                    while(i!= imIn.end(row)) {
                        (*j) = (*i).blue();
                        ++i; ++j;
                    }
                }
                while (imIn.nextRow(row));
             break;

             // Default
             default:
                error("extractColorChannel", "invalid color channel");
                return 1;
        }
        return 0;
}

template<class Tout>
int PreProcessFundus<Tout>::medianFilter(pip::Image<unsigned char> &imInOut)
{
        pip::Image<unsigned char> imTemp(imInOut);

        int iKernelCenter = iMedianWidth/2;
        int iWidth  = imInOut.dim(0);
        int iHeight = imInOut.dim(1);

        int iX, iY;

        int iNrPixelsLEQMedian = 1 + (iMedianWidth * iMedianWidth)/2;
        vector<int> hist(256);

        for (int y=0; y<iHeight; ++y)
        for (int x=0; x<iWidth; ++x) {

                // Reset histogram values
                for (int i=0; i<256; ++i)
                hist[i] = 0;

                // Assume non-addressable pixels have value 0
                for (int m=0; m<iMedianWidth; ++m)
                for (int n=0; n<iMedianWidth; ++n) {
                        iX = x+(m-iKernelCenter);
                        iY = y+(n-iKernelCenter);
                        if ( iX >= 0 && iX < iWidth && iY >= 0 && iY < iHeight)
                        hist[imTemp(iX,iY)]++;
                        else
                        hist[0]++;
                }

                // Find the median in the cumulative histogram
                int iLTMedian = 0;
                int iMedian = -1;
                while (iLTMedian < iNrPixelsLEQMedian) {
                        iMedian++;
                        iLTMedian += hist[iMedian];
                }

                imInOut(x,y) = iMedian;
        }

        return 0;
}

template<class Tout>
int PreProcessFundus<Tout>::run(const pip::Image<pip::Color<unsigned char> >& imIn, pip::Image<Tout>& imOut){
        // extract the selected color channel from imIn
        // median filter the gray-scale image
        // subtract the median filtered image from the gray-scale image
        //   and put the result in imOut

        // Get gray-scale image
        int errors = 0;
        pip::Image<unsigned char> imTemp; // Temporary gray-scale image
        errors = extractColorChannel(imIn,imTemp);
        if(errors)
            return errors;

        // Copy imTemp to imOut
        if (imOut.dimension()!= imIn.dimension()) imOut.resize(imTemp.dimension());
        PixelIterator<unsigned char> i;
        PixelIterator<Tout> j;
        pip::Dimension row = 0;
        do {
            i = imTemp.begin(row);
            j = imOut.begin(row);
            while(i!= imTemp.end(row)) {
                (*j) = (Tout)(*i);
                ++i; ++j;
            }
        }
        while (imTemp.nextRow(row));


        // Temporary median filtered gray-scale image
        errors = medianFilter(imTemp);
        if(errors) return errors;

        // Find min/max/average before subtracting the filtered image
        Tout min, max, avr;
        getMinMaxAvrImage<Tout>(imOut,min, max, avr);
        cout << "Before subtraction :" <<endl;
        cout << "min : "<<min<<", max : "<<max<<", average : "<<avr<<endl;
        exportFile(imOut,"fundus_G.pip"); // Write image to disk

        // Final Image: gray-scale image minus median filtered image.
        row = 0;
        do {
                i = imTemp.begin(row); // median filtered image
                j = imOut.begin(row);  // original gray scale image
                while(i!= imTemp.end(row)) {
                    (*j) -= (*i); // subtract median from original
                    ++i; ++j;
                }
        }
        while (imTemp.nextRow(row));

        getMinMaxAvrImage<Tout>(imOut,min, max, avr);
        cout << "After subtraction :" <<endl;
        cout << "min : "<<min<<", max : "<<max<<", average : "<<avr<<endl;

        return(errors);
}
#endif